package com.xj.opcua.client;

import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Security;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xj.opcua.client.sert.KeyStoreLoader;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

public class ClientRunner {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private final Logger logger = LoggerFactory.getLogger(getClass());
    
    public Client client = null;
    
    public ClientRunner(){}
    
    public ClientRunner(Client client) throws Exception {
        this(client, true);
    }

    public ClientRunner(Client client, boolean serverRequired) throws Exception {
        this.client = client;
    }

   
    
    private int count = 0;
    public void run() {
    	CompletableFuture<OpcUaClient> future = new CompletableFuture<>();
        OpcUaClient opcUaClient;;
        try {
        	//	创建客户端
            opcUaClient = createClient();
            opcUaClient.connect().get();
            List<Object> list = new LinkedList<Object>();
            list.add(opcUaClient);
            list.add(future);
            Client.map.put(client.getEndpointUrl(), list);
        } catch (Throwable t) {
        	count++;
            logger.error("Error getting client: "+t.getMessage());
            logger.info(client.getEndpointUrl()+"  connect ..."+count);
            run();
        }

    }
    
    /**
     * 创建客户端，配置客户端连接属性
     * @return
     * @throws Exception
     */
    public OpcUaClient createClient() throws Exception {
        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
        Files.createDirectories(securityTempDir);
        if (!Files.exists(securityTempDir)) {
            throw new Exception("unable to create security dir: " + securityTempDir);
        }

        LoggerFactory.getLogger(getClass())
            .info("security temp dir: {}", securityTempDir.toAbsolutePath());

        KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
        String hostName = InetAddress.getLocalHost().getHostName();
		String applicationName = "myClient";
		String applicationUri = "urn:"+hostName+":OPCUA:"+applicationName;
		
        return create(
        		client.getEndpointUrl(),
            endpoints ->
                endpoints.stream()
                    .filter(client.endpointFilter())
                    .findFirst(),
            configBuilder ->
                configBuilder
                    .setApplicationName(LocalizedText.english(applicationName))
                    .setApplicationUri(applicationUri)
                    .setCertificate(loader.getClientCertificate())
                    .setKeyPair(loader.getClientKeyPair())
                    .setIdentityProvider(client.getIdentityProvider())
                    .setRequestTimeout(uint(5000))
                    .build()
        );
    }
    
    private static OpcUaClient create(
            String endpointUrl,
            Function<List<EndpointDescription>, Optional<EndpointDescription>> selectEndpoint,
            Function<OpcUaClientConfigBuilder, OpcUaClientConfig> buildConfig
        ) throws UaException {

        try {
            List<EndpointDescription> endpoints =
                DiscoveryClient.getEndpoints(endpointUrl).get();

            EndpointDescription endpoint = new EndpointDescription(
            		endpointUrl,
					endpoints.get(0).getServer(),
					endpoints.get(0).getServerCertificate(),
					endpoints.get(0).getSecurityMode(),
					endpoints.get(0).getSecurityPolicyUri(),
					endpoints.get(0).getUserIdentityTokens(),
					endpoints.get(0).getTransportProfileUri(),
					endpoints.get(0).getSecurityLevel()
	         );
            OpcUaClientConfigBuilder builder = OpcUaClientConfig.builder()
                .setEndpoint(endpoint);

            return OpcUaClient.create(buildConfig.apply(builder));
        } catch (InterruptedException | ExecutionException e) {
            if (!endpointUrl.endsWith("/discovery")) {
                StringBuilder discoveryUrl = new StringBuilder(endpointUrl);
                if (!endpointUrl.endsWith("/")) {
                    discoveryUrl.append("/");
                }
                discoveryUrl.append("discovery");

                return create(discoveryUrl.toString(), selectEndpoint, buildConfig);
            } else {
                throw UaException.extract(e)
                    .orElseGet(() -> new UaException(e));
            }
        }
    }

}
